/***************************** BEGIN LICENSE BLOCK ***************************

 The contents of this file are subject to the Mozilla Public License Version
 1.1 (the "License"); you may not use this file except in compliance with
 the License. You may obtain a copy of the License at
 http://www.mozilla.org/MPL/MPL-1.1.html
 
 Software distributed under the License is distributed on an "AS IS" basis,
 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 for the specific language governing rights and limitations under the License.
 
 The Original Code is the "EO-SPS Framework".
 
 The Initial Developer of the Original Code is Spotimage S.A.
 Portions created by the Initial Developer are Copyright (C) 2007
 the Initial Developer. All Rights Reserved.
 
 Contributor(s): 
    Alexandre Robin <alexandre.robin@spotimage.fr>
 
******************************* END LICENSE BLOCK ***************************/

package com.spotimage.eosps;

import org.vast.cdm.common.DataBlock;
import org.vast.cdm.common.DataComponent;
import org.vast.data.AbstractDataBlock;
import org.vast.data.DataArray;
import org.vast.data.DataBlockList;
import org.vast.data.DataValue;
import org.vast.data.ParamHelper;
import org.vast.ows.sps.StatusReport;
import org.vast.ows.sps.TaskingResponse;
import org.vast.sweCommon.SWEData;
import org.vast.util.DateTime;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;


/**
 * <p><b>Title:</b><br/>
 * EO Report Helper
 * </p>
 *
 * <p><b>Description:</b><br/>
 * Helper to get and set extended data specific to spaceborne earth
 * observation instruments from/to SPS responses. More specific helpers
 * are also available for optical and SAR instruments.<br/>
 * This class must be constructed from an existing GetFeasibility,
 * Submit, GetStatus, Update, Reserve or Confirm response (for instance
 * as obtained using the generic SPS deserializers).
 * 
 * TODO implement unit conversion in all get/set methods dealing with 
 * decimal values
 * 
 * </p>
 *
 * <p>Copyright (c) 2008, Spot Image</p>
 * @author Alexandre Robin <alexandre.robin@spotimage.fr>
 * @date Dec, 18th 2008
 * @since 1.0
 */
public class EOReportHelper extends ParamHelper implements EOConstants
{
	protected DataComponent reportParams;
	protected SWEData reportData;
	protected DataArray segmentsArray;
	protected DataArray gridCellsArray;
	protected DataComponent currentComponent;
	
	
	/**
	 * Constructor using a report that already contains
	 * properly initialized extended data
	 * @param report
	 */
	public EOReportHelper(StatusReport report)
	{
		this.reportData = report.getExtendedData();
		loadReportData(this.reportData);
	}
	
	
	/**
	 * Constructor using a response that already contains
	 * properly initialized extended data
	 * @param response TaskingResponse response object containing report data
	 */
	public EOReportHelper(TaskingResponse response)
	{
		this(response.getReport());
	}
		
	
	/**
	 * Constructor using a report without extended data
	 * and the report component structure
	 * @param report report object receiving report data
	 * @param reportParams report data structure definition (from DescribeTasking)
	 */
	public EOReportHelper(StatusReport report, DataComponent reportParams)
	{
		reportData = new SWEData();
		reportData.setDataComponents(reportParams.copy());
		report.setExtendedData(reportData);
		loadReportData(this.reportData);
	}
	
	
	/**
	 * Constructor using a response without extended data
	 * and the report component structure
	 * @param response TaskingResponse response object receiving report data
	 * @param reportParams report data structure definition (from DescribeTasking)
	 */
	public EOReportHelper(TaskingResponse response, DataComponent reportParams)
	{
		this(response.getReport(), reportParams);
	}
	
	
	public DataArray findSegmentList()
	{
		if (segmentsArray == null)
			segmentsArray = (DataArray)this.findParameterByDefinition(SEGMENT_LIST_URI);
		
		return segmentsArray;
	}
	
	
	public boolean hasSegments()
	{
		return (findSegmentList() != null);
	}


	public DataArray findGridCellList()
	{
		if (gridCellsArray == null)
			gridCellsArray = (DataArray)this.findParameterByDefinition(CELL_LIST_URI);
		
		return gridCellsArray;
	}
	
	
	public boolean hasGridCells()
	{
		return (findGridCellList() != null);
	}


	/**
	 * Sets the total number of segments to be added to the report
	 * This needs to be called before adding any segment data
	 * @param numSegments
	 */
	public void setNumSegments(int numSegments)
	{
		DataArray array = findSegmentList();
		if (array != null)
			array.updateSize(numSegments);
	}
	
	
	/**
	 * Retrieves the number of segments included in this report
	 * @return
	 */
	public int getNumSegments()
	{
		DataArray array = findSegmentList();
		if (array != null)
			return array.getComponentCount();
		else
			return 0;
	}
	
	
	/**
	 * Sets the total number of grid cells to be added to the report
	 * This needs to be called before adding any grid cell data
	 * @param numCells
	 */
	public void setNumGridCells(int numCells)
	{
		DataArray array = findGridCellList();
		if (array != null)
			array.updateSize(numCells);
	}
	
	
	/**
	 * Retrieves the number of grid cells included in this report
	 * @return
	 */
	public int getNumGridCells()
	{
		DataArray array = findGridCellList();
		if (array != null)
			return array.getComponentCount();
		else
			return 0;
	}
	

	/**
	 * Prepare the helper to load or read data from the specified segment
	 * This or addSegment needs to be called before adding data to this segment
	 * @param index
	 */
	public void loadSegment(int index)
	{
		DataArray array = findSegmentList();
		if (array == null)
			return;
		
		currentComponent = array.getComponent(index);
	}
	
	
	/**
	 * Adds a segment slot to the list of segment and prepare to set its properties 
	 * This or loadSegment needs to be called before adding data to this segment
	 */
	public void addSegment()
	{
		DataArray array = findSegmentList();
		if (array == null)
			return;
		
		// update array size w/o resizing data block!
		int arraySize = array.getComponentCount();
		array.updateSizeComponent(arraySize+1);
		
		// add data block for one more segment
		AbstractDataBlock childBlock = ((DataBlockList)array.getData()).get(0);
		((DataBlockList)array.getData()).add(childBlock.renew());
		
		currentComponent = array.getComponent(arraySize);
	}
	
	
	/**
	 * Prepare the helper to load or read data from the specified grid cell
	 * This needs to be called before adding data to this grid cell
	 * @param index
	 */
	public void loadGridCell(int index)
	{
		DataArray array = findGridCellList();
		if (array == null)
			return;
		
		currentComponent = array.getComponent(index);
	}


	/**
	 * Finds cell or segment ID in report structure
	 * @return
	 */
	public DataValue findID()
	{
		DataComponent param = this.findParameterByDefinition(OBJECT_ID_URI, currentComponent);
		return (DataValue) param;
	}

	public String getID()
	{
		DataValue param = findID();
		if (param != null)
			return param.getData().getStringValue();
		else
			return null;
	}

	public void setID(String id)
	{
		DataValue param = findID();
		if (param != null)
			param.getData().setStringValue(id);
	}
	
	
	/**
	 * Finds cell or segment status in report structure
	 * @return
	 */
	public DataValue findStatus()
	{
		DataComponent param = this.findParameterByDefinition(STATUS_URI, currentComponent);
		return (DataValue)param;
	}
	
	public String getStatus()
	{
		DataValue param = findStatus();
		if (param != null)
			return param.getData().getStringValue();
		else
			return null;
	}	
	
	public void setStatus(String statusCode)
	{
		DataValue param = findStatus();
		if (param != null)
			param.getData().setStringValue(statusCode);
	}


	/**
	 * Finds segment platform ID in report structure
	 * @return
	 */
	public DataValue findPlatformID()
	{
		DataComponent param = this.findParameterByDefinition(PLATFORM_ID_URI, currentComponent);
		return (DataValue) param;
	}

	public String getPlatformID()
	{
		DataValue param = findPlatformID();
		if (param != null)
			return param.getData().getStringValue();
		else
			return null;
	}

	public void setPlatformID(String platform)
	{
		DataValue param = findPlatformID();
		if (param != null)
			param.getData().setStringValue(platform);
	}
	
	
	/**
	 * Finds segment platform name in report structure
	 * @return
	 */
	public DataValue findPlatformName()
	{
		DataComponent param = this.findParameterByDefinition(PLATFORM_NAME_URI, currentComponent);
		return (DataValue) param;
	}

	public String getPlatformName()
	{
		DataValue param = findPlatformName();
		if (param != null)
			return param.getData().getStringValue();
		else
			return null;
	}

	public void setPlatformName(String platform)
	{
		DataValue param = findPlatformName();
		if (param != null)
			param.getData().setStringValue(platform);
	}


	/**
	 * Finds segment instrument ID in report structure
	 * @return
	 */	
	public DataValue findInstrumentID()
	{
		DataComponent param = this.findParameterByDefinition(INSTRUMENT_ID_URI, currentComponent);
		return (DataValue) param;
	}

	public String getInstrumentID()
	{
		DataValue param = findInstrumentID();
		if (param != null)
			return param.getData().getStringValue();
		else
			return null;
	}

	public void setInstrumentID(String instrument)
	{
		DataValue param = findInstrumentID();
		if (param != null)
			param.getData().setStringValue(instrument);
	}
	
	
	/**
	 * Finds segment instrument name in report structure
	 * @return
	 */	
	public DataValue findInstrumentName()
	{
		DataComponent param = this.findParameterByDefinition(INSTRUMENT_NAME_URI, currentComponent);
		return (DataValue) param;
	}

	public String getInstrumentName()
	{
		DataValue param = findInstrumentName();
		if (param != null)
			return param.getData().getStringValue();
		else
			return null;
	}

	public void setInstrumentName(String instrument)
	{
		DataValue param = findInstrumentName();
		if (param != null)
			param.getData().setStringValue(instrument);
	}


	/**
	 * Finds segment instrument mode in report structure
	 * @return
	 */
	public DataValue findInstrumentMode()
	{
		DataComponent param = this.findParameterByDefinition(INSTRUMENT_MODE_URI, currentComponent);
		return (DataValue) param;
	}

	public String getInstrumentMode()
	{
		DataValue param = findInstrumentMode();
		if (param != null)
			return param.getData().getStringValue();
		else
			return null;
	}

	public void setInstrumentMode(String instrumentMode)
	{
		DataValue param = findInstrumentMode();
		if (param != null)
			param.getData().setStringValue(instrumentMode);
	}


	/**
	 * Finds segment acquisition start date in report structure
	 * @return
	 */
	public DataValue findAcquisitionStart()
	{
		DataComponent param = this.findParameterByDefinition(ACQ_START_URI, currentComponent);
		return (DataValue)param;
	}
	
	public DateTime getAcquisitionStart()
	{
		DataValue param = findAcquisitionStart();
		if (param != null)
			return new DateTime(param.getData().getDoubleValue());
		else
			return null;
	}
	
	public void setAcquisitionStart(DateTime acquisitionDate)
	{
		DataValue param = findAcquisitionStart();
		if (param != null)
			param.getData().setDoubleValue(acquisitionDate.getJulianTime());
	}	
	
	
	/**
	 * Finds segment acquisition stop date in report structure
	 * @return
	 */
	public DataValue findAcquisitionStop()
	{
		DataComponent param = this.findParameterByDefinition(ACQ_STOP_URI, currentComponent);
		return (DataValue) param;
	}
	
	public DateTime getAcquisitionStop()
	{
		DataValue param = findAcquisitionStop();
		if (param != null)
			return new DateTime(param.getData().getDoubleValue());
		else
			return null;
	}

	public void setAcquisitionStop(DateTime acqStopDate)
	{
		DataValue param = findAcquisitionStop();
		if (param != null)
			param.getData().setDoubleValue(acqStopDate.getJulianTime());
	}

	
	/**
	 * Finds segment elevation incidence in report structure
	 * @return
	 */
	public DataValue findElevationIncidence()
	{
		DataComponent param = this.findParameterByDefinition(ELEV_INC_URI, currentComponent);
		return (DataValue) param;
	}

	public double getElevationIncidence()
	{
		DataValue param = findElevationIncidence();
		if (param != null)
			return param.getData().getDoubleValue();
		else
			return Double.NaN;
	}

	public void setElevationIncidence(double incidence)
	{
		DataValue param = findElevationIncidence();
		if (param != null)
			param.getData().setDoubleValue(incidence);
	}


	/**
	 * Finds segment azimuth incidence in report structure
	 * @return
	 */
	public DataValue findAzimuthIncidence()
	{
		DataComponent param = this.findParameterByDefinition(AZIM_INC_URI, currentComponent);
		return (DataValue) param;
	}
	
	public double getAzimuthIncidence()
	{
		DataValue param = findAzimuthIncidence();
		if (param != null)
			return param.getData().getDoubleValue();
		else
			return Double.NaN;
	}

	public void setAzimuthIncidence(double incidence)
	{
		DataValue param = findAzimuthIncidence();
		if (param != null)
			param.getData().setDoubleValue(incidence);
	}


	/**
	 * Finds cell or segment footprint in report structure
	 * @return
	 */
	public DataComponent findFootprint()
	{
		DataComponent param = this.findParameterByDefinition(POLYGON_URI, currentComponent);
		return param;
	}
	
	public Polygon getFootprint()
	{
		DataComponent param = findFootprint();
		if (param == null)
			return null;
		
		DataArray exterior = (DataArray)param.getComponent(0);
		int numPoint = exterior.getComponentCount();

		// read all points
		GeometryFactory fac = new GeometryFactory();
		Coordinate[] coordinates = new Coordinate[numPoint];
		for (int p = 0; p < numPoint; p++)
		{
			Coordinate coords = new Coordinate();
			DataComponent point = exterior.getComponent(p);
			coords.x = point.getComponent("Lon").getData().getDoubleValue();
			coords.y = point.getComponent("Lat").getData().getDoubleValue();
			coordinates[p] = coords;
		}

		LinearRing outer = fac.createLinearRing(coordinates);
		Polygon poly = fac.createPolygon(outer, null);
		return poly;
	}

	public void setFootprint(Polygon footprint)
	{
		DataComponent param = findFootprint();
		if (param == null)
			return;
		
		DataArray exterior = (DataArray)param.getComponent(0);//exterior
		int numPoints = footprint.getExteriorRing().getNumPoints();
		exterior.updateSize(numPoints);
		for (int p = 0; p < numPoints; p++)
		{
			Coordinate coord = footprint.getExteriorRing().getCoordinateN(p);
			exterior.getComponent(p).getComponent("Lat").getData().setDoubleValue(coord.y);
			exterior.getComponent(p).getComponent("Lon").getData().setDoubleValue(coord.x);
		}
	}
	
	
	/**
	 * Finds orbit number in report structure
	 * @return
	 */
	public DataValue findOrbitNumber()
	{
		DataComponent param = this.findParameterByDefinition(ORBIT_URI, currentComponent);
		return (DataValue)param;
	}
	
	public int getOrbitNumber()
	{
		DataValue param = findOrbitNumber();
		if (param != null)
			return param.getData().getIntValue();
		else
			return -1;
	}	
	
	public void setOrbitNumber(int orbitNumber)
	{
		DataValue param = findOrbitNumber();
		if (param != null)
			param.getData().setIntValue(orbitNumber);		
	}
	
	
	/**
	 * Finds track number in report structure
	 * @return
	 */
	public DataValue findTrackNumber()
	{
		DataComponent param = this.findParameterByDefinition(TRACK_URI, currentComponent);
		return (DataValue)param;
	}
	
	public int getTrackNumber()
	{
		DataValue param = findTrackNumber();
		if (param != null)
			return param.getData().getIntValue();
		else
			return -1;
	}	
	
	public void setTrackNumber(int trackNumber)
	{
		DataValue param = findTrackNumber();
		if (param != null)
			param.getData().setIntValue(trackNumber);		
	}
	
	
	/**
	 * Finds frame number in report structure
	 * @return
	 */
	public DataValue findFrameNumber()
	{
		DataComponent param = this.findParameterByDefinition(FRAME_URI, currentComponent);
		return (DataValue)param;
	}
	
	public int getFrameNumber()
	{
		DataValue param = findFrameNumber();
		if (param != null)
			return param.getData().getIntValue();
		else
			return -1;
	}	
	
	public void setFrameNumber(int frameNumber)
	{
		DataValue param = findFrameNumber();
		if (param != null)
			param.getData().setIntValue(frameNumber);		
	}
	
	
	/**
	 * Finds stereo segment ID in report structure
	 * @return
	 */
	public DataValue findStereoSegmentID()
	{
		DataComponent param = this.findParameterByDefinition(STEREO_ID_URI, currentComponent);
		return (DataValue) param;
	}

	public String getStereoSegmentID()
	{
		DataValue param = findStereoSegmentID();
		if (param != null)
			return param.getData().getStringValue();
		else
			return null;
	}

	public void setStereoSegmentID(String stereoID)
	{
		DataValue param = findStereoSegmentID();
		if (param != null)
			param.getData().setStringValue(stereoID);
	}
	
		
	/**
	 * Finds cell success rate in report structure
	 * @return
	 */
	public DataValue findSuccessRate()
	{
		DataComponent param = this.findParameterByDefinition(SUCCESS_RATE_URI, currentComponent);
		return (DataValue)param;
	}
	
	public double getSuccessRate()
	{
		DataValue param = findSuccessRate();
		if (param != null)
			return param.getData().getDoubleValue();
		else
			return Double.NaN;
	}	
	
	public void setSuccessRate(double successRate)
	{
		DataValue param = findSuccessRate();
		if (param != null)
			param.getData().setDoubleValue(successRate);		
	}
	
	
	/**
	 * Finds cell estimated success date in report structure
	 * @return
	 */
	public DataValue findEstimatedSuccessDate()
	{
		DataComponent param = this.findParameterByDefinition(SUCCESS_DATE_URI, currentComponent);
		return (DataValue) param;
	}
	
	public DateTime getEstimatedSuccessDate()
	{
		DataValue param = findEstimatedSuccessDate();
		if (param != null)
			return new DateTime(param.getData().getDoubleValue());
		else
			return null;
	}

	public void setEstimatedSuccessDate(DateTime successDate)
	{
		DataValue param = findEstimatedSuccessDate();
		if (param != null)
			param.getData().setDoubleValue(successDate.getJulianTime());
	}
	
	
	/**
	 * Finds cell next attempt date in report structure
	 * @return
	 */
	public DataValue findNextAttemptDate()
	{
		DataComponent param = this.findParameterByDefinition(NEXT_ATTEMPT_DATE_URI, currentComponent);
		return (DataValue) param;
	}
	
	public DateTime getNextAttemptDate()
	{
		DataValue param = findNextAttemptDate();
		if (param != null)
			return new DateTime(param.getData().getDoubleValue());
		else
			return null;
	}

	public void setNextAttemptDate(DateTime nextAttemptDate)
	{
		DataValue param = findNextAttemptDate();
		if (param != null)
			param.getData().setDoubleValue(nextAttemptDate.getJulianTime());
	}
	
	
	/**
	 * Finds cell number of remaining attempts in report structure
	 * @return
	 */
	public DataValue findRemainingAttempts()
	{
		DataComponent param = this.findParameterByDefinition(REMAINING_ATTEMPTS_URI, currentComponent);
		return (DataValue)param;
	}
	
	public int getRemainingAttempts()
	{
		DataValue param = findRemainingAttempts();
		if (param != null)
			return param.getData().getIntValue();
		else
			return -1;
	}	
	
	public void setRemainingAttempts(int remainingAttempts)
	{
		DataValue param = findRemainingAttempts();
		if (param != null)
			param.getData().setIntValue(remainingAttempts);		
	}
		
	
	/**
	 * Finds percent completion in report structure
	 * @return
	 */
	public DataValue findPercentCompletion()
	{
		DataComponent param = this.findParameterByDefinition(PERCENT_COMPLETION_URI);
		return (DataValue) param;
	}

	public double getPercentCompletion()
	{
		DataValue param = findPercentCompletion();
		if (param != null)
			return param.getData().getDoubleValue();
		
		return Double.NaN;
	}

	public void setPercentCompletion(double completed)
	{
		DataValue param = findPercentCompletion();
		if (param != null)
			param.getData().setDoubleValue(completed);
	}
	
	
	/**
	 * Finds segment preview URL in report structure 
	 * @return
	 */
	public DataValue findPreviewUrl()
	{
		DataComponent param = findParameterByDefinition(PREVIEW_URI, currentComponent);
		return (DataValue)param;
	}
	
	public String getPreviewUrl()
	{
		DataValue param = findPreviewUrl();
		if (param != null)
			return param.getData().getStringValue();

		return null;
	}
	
	public void setPreviewUrl(String previewUrl)
	{
		DataValue param = findPreviewUrl();
		if (param != null)
			param.getData().setStringValue(previewUrl);
	}
	
	
	/**
	 * Finds estimated cost in report structure
	 * @return
	 */
	public DataValue findEstimatedCost()
	{
		DataComponent param = findParameterByDefinition(ESTIMATED_COST_URI);
		return (DataValue)param;
	}
	
	public double getEstimatedCost()
	{
		DataValue param = findEstimatedCost();
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue();	
	}
	
	public void setEstimatedCost(double cost)
	{
		DataValue param = findEstimatedCost();
		if (param != null)
			param.getData().setDoubleValue(cost);
	}


	/**
	 * Finds a parameter with the given name anywhere in the
	 * tasking parameters structure
	 * @param name
	 * @return
	 */
	public DataComponent findParameterByName(String name)
	{
		return findParameterByName(name, reportParams);
	}


	/**
	 * Finds a parameter with the given definition URI anywhere
	 * in the tasking parameters structure. 
	 * @param defUri
	 * @return
	 */
	public DataComponent findParameterByDefinition(String defUri)
	{
		return findParameterByDefinition(defUri, reportParams);
	}


	/**
	 * Initializes the block of data when this helper is used
	 * for setting values of a new request
	 * @param taskingData
	 * @param useForWriting
	 */
	protected void loadReportData(SWEData responseData)
	{
		reportParams = responseData.getDataComponents();
		
		// prepare response for writing!
		if (responseData.getDataList().getComponentCount() == 0)
		{
			reportParams.assignNewDataBlock();
			responseData.getDataList().addData(reportParams.getData());
		}

		// for reading, simply load first block
		else
		{
			DataBlock paramData = responseData.getDataList().getComponent(0).getData();
			reportParams.setData(paramData);
		}
	}
}